package de.koller.worlders.engine.entity.mo.wphysics;

import de.koller.oceanFramework.OceanException;
import de.koller.oceanFramework.math.IntersectionUtil2D;
import de.koller.oceanFramework.math.Vec;
import de.koller.worlders.engine.entity.IAABB;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class AABBIntersection {
	
	private AABBIntersection() {}

	public static AABBImpact impact( Vec way, IAABB dyn, IAABB sta ) {
		if( !isInRange( way, dyn, sta ) )
			return null;
		
		List<AABBImpact> impacts = new ArrayList<>();
		
		Vec[] vdyn = verteces( dyn );
		for( Vec v : vdyn )
			for( CollisionSide c : CollisionSide.values() ) {
				try {
					float t = toi( v, way, sta, c );
					AABBImpact ai = new AABBImpact( c.getInverse(), c, t, sta );
					impacts.add( ai );
				} catch( NoIntersectionException ex ) {
					
				}
			}
		
		Vec[] vsta = verteces( sta );
		Vec iway = new Vec( -way.getX(), -way.getY() );
		for( Vec v : vsta )
			for( CollisionSide c : CollisionSide.values() ) {
				try {
					float t = toi( v, iway, dyn, c );
					AABBImpact ai = new AABBImpact( c, c.getInverse(), t, sta );
					impacts.add( ai );
				} catch( NoIntersectionException ex ) {
					
				}
			}
		
		if( impacts.isEmpty() )
			return null;
		return Collections.min( impacts );
	}

	private static boolean isInRange( Vec way, IAABB dyn, IAABB sta ) {
		double disX =	dyn.getSize().getX() + sta.getSize().getX() + Math.abs( way.getX() );
		double disY =	dyn.getSize().getY() + sta.getSize().getY() + Math.abs( way.getY() );
		if( Math.abs( sta.getPosition().getX() - dyn.getPosition().getX() ) > disX )
			return false;
		if( Math.abs( sta.getPosition().getY() - dyn.getPosition().getY() ) > disY )
			return false;
		return true;
	}	
	
	private static Vec[] verteces( IAABB obj ) {
		return new Vec[] {
			new Vec(	obj.getPosition().getX(),
						obj.getPosition().getY() ),
			
			new Vec(	obj.getPosition().getX(),
						obj.getPosition().getY() + obj.getSize().getY() ),
			
			new Vec(	obj.getPosition().getX() + obj.getSize().getX(),
						obj.getPosition().getY() + obj.getSize().getY() ),
			
			new Vec(	obj.getPosition().getX() + obj.getSize().getX(),
						obj.getPosition().getY() )
		};
	}
	
	private static float toi( Vec p, Vec n, IAABB obj, CollisionSide s ) throws NoIntersectionException {
		Vec[] b = lineSegment( obj, s );
		double toi = IntersectionUtil2D.intersection( p, n, b[0], b[1] );
		
		if( toi > 1 || toi < 0 )
			throw new NoIntersectionException();
	
		Vec impact = new Vec( p.getX() + n.getX() * toi, p.getY() + n.getY() * toi );
		
		if( s == CollisionSide.Above || s == CollisionSide.Bottom ) {
			if( impact.getX() > b[1].getX() || impact.getX() < b[0].getX() )
				throw new NoIntersectionException();
		} else {
			if( impact.getY() > b[1].getY() || impact.getY() < b[0].getY() )
				throw new NoIntersectionException();
		}
		
		return (float)toi;
	}
	
	private static Vec[] lineSegment( IAABB obj, CollisionSide side ) {
		Vec[] p = new Vec[] { new Vec(), new Vec() };
		
		switch( side ) {
			case Above:
				p[0].set( obj.getPosition().getX(),
						  obj.getPosition().getY() );
				p[1].set( obj.getPosition().getX() + obj.getSize().getX(),
						  obj.getPosition().getY() );
				return p;
						
			case Bottom:
				p[0].set( obj.getPosition().getX(),
						  obj.getPosition().getY() + obj.getSize().getY() );
				p[1].set( obj.getPosition().getX() + obj.getSize().getX(),
						  obj.getPosition().getY() + obj.getSize().getY() );
				return p;
				
			case Left:
				p[0].set( obj.getPosition().getX(),
						  obj.getPosition().getY() );
				p[1].set( obj.getPosition().getX(),
						  obj.getPosition().getY() + obj.getSize().getY() );
				return p;
				
			case Right:
				p[0].set( obj.getPosition().getX() + obj.getSize().getX(),
						  obj.getPosition().getY() );
				p[1].set( obj.getPosition().getX() + obj.getSize().getX(),
						  obj.getPosition().getY() + obj.getSize().getY() );
				return p;
		}
		
		throw new OceanException( "unreachable!" );
	}
	
	private static class NoIntersectionException extends Exception {
		
	}
	
	public static class AABBImpact implements Comparable<AABBImpact> {
		public final CollisionSide dyn;
		public final CollisionSide sta;
		public float timeOfImpact;
		public final IAABB staObj;

		private AABBImpact( CollisionSide dyn, CollisionSide sta, float timeOfImpact, IAABB staObj ) {
			this.dyn = dyn;
			this.sta = sta;
			this.timeOfImpact = timeOfImpact;
			this.staObj = staObj;
		}

		@Override
		public int compareTo( AABBImpact o ) {
			return Float.compare( timeOfImpact, o.timeOfImpact );
		}
		
	}
	
	public static enum CollisionSide {
		Above, Bottom, Left, Right;
		
		public CollisionSide getInverse() {
			switch( this ) {
				case Above:
					return Bottom;
				case Bottom:
					return Above;
				case Left:
					return Right;
				case Right:
					return Left;
			};
			
			throw new OceanException( "unreachable!" );
		}
		
	}
	
}
